#ifndef PROTOBUF_DISPATCHER_H
#define PROTOBUF_DISPATCHER_H

#include <boost/noncopyable.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>

#include <google/protobuf/message.h>

#include <muduo/net/TcpConnection.h>
#include <muduo/base/Timestamp.h>

using namespace muduo;
using namespace muduo::net;

namespace sdfs
{
namespace protobuf
{
typedef boost::shared_ptr<google::protobuf::Message> MessagePtr;

typedef boost::function<void (const muduo::net::TcpConnectionPtr&, const MessagePtr&, muduo::Timestamp)> ProtobufMessageCallback;

class ProtobufCallback : boost::noncopyable
{
public:
	virtual ~ProtobufCallback(){};
	
	virtual void onMessage(const muduo::net::TcpConnectionPtr& conn,const MessagePtr& message, muduo::Timestamp timestamp) = 0;
};

template <typename T>
class ProtobufCallbackT : public ProtobufCallback
{
public:

	typedef boost::function<void (const TcpConnectionPtr&, const boost::shared_ptr<T>&, muduo::Timestamp)> ProtobufMessageCallbackT;

	ProtobufCallbackT(const ProtobufMessageCallbackT& callback): callback_(callback)
	{
	}

	virtual void onMessage(const muduo::net::TcpConnectionPtr& conn, 
		const MessagePtr& message, muduo::Timestamp timestamp)
	{
		boost::shared_ptr<T> concrete = boost::static_pointer_cast<T>(message);
		assert(concrete !=NULL);
		callback_(conn, concrete, timestamp);
	}

private:
	ProtobufMessageCallbackT callback_;
};

class ProtobufDispatcher : boost::noncopyable
{
public:
	
	ProtobufDispatcher(const ProtobufMessageCallback& callback):
		defaultMessageCallback_(callback)
	{
		
	}

	void onProtobufMessage(const muduo::net::TcpConnectionPtr& conn,
			const MessagePtr& message, muduo::Timestamp timestamp)
	{
		MessageCallbackMap::const_iterator it = callbacks_.find(message->GetDescriptor());
		if( it != callbacks_.end() )
		{
			it->second->onMessage(conn, message, timestamp);	
		}
		else
		{
			defaultMessageCallback_(conn, message, timestamp);
		}
	}

	template <typename T>
	void registerProtobufMessageCallback(const typename ProtobufCallbackT<T>::ProtobufMessageCallbackT& cb)
	{
		boost::shared_ptr<ProtobufCallbackT<T> > pcb(new ProtobufCallbackT<T>(cb));
		callbacks_[T::descriptor()] = pcb;
	}

private:
	typedef std::map<const google::protobuf::Descriptor*, boost::shared_ptr<ProtobufCallback> > MessageCallbackMap;

	MessageCallbackMap callbacks_;
	ProtobufMessageCallback defaultMessageCallback_;
};
}
}
#endif
